package com.youyicloud.sdk;

import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

/**
 * Created by yucongzheng on 2017/8/17.
 */

public class Crypt {
    private static final String TAG = "#Crypt";

    public static final int KEYLEN = 16;

    private static final int ROUNDS = 16;
    private static final int LOG_ROUNDS = 4;
    private static final long DELTA = 0x9e3779b9L;
    private static final long BASE = 0x0ffffffffL;

    public static int SALT_LEN = 2;
    public static int ZERO_LEN = 7;

    /**
     * 加密
     * @param version 版本 目前可填1或者2
     * @param inputBuffer 明文
     * @param inputBufferLen 明文长度
     * @param outputBuffer 密文（需先开好足够大的空间）
     * @return 密文长度
     * 加密主要逻辑：
     * plainBuf XOR prev_cryptBuf -> plainTeaBuf
     * plainTeaBuf -> (Using Tea Algorithm To Encrypt) -> cryptTeaBuf
     * cryptTeaBuf XOR prev_plainTeaBuf -> cryptBuf
     */
    public static synchronized int encrypt(byte version, ByteBuffer inputBuffer, int inputBufferLen, byte[] key, ByteBuffer outputBuffer) {

        if (version != 1 && version != 2 ) {
            throw new RuntimeException("version must be 1 or 2");
        }

        if (key.length != KEYLEN) {
            return -1;
        }
        ByteBuffer keyBuf = ByteBuffer.allocate(16);
        for (int i=0; i<KEYLEN; i++) {
            keyBuf.put(i, key[i]);
        }

        int inputIndex = 0, outputIndex = 0;

        int nPadSaltBodyZeroLen = inputBufferLen + 1 + SALT_LEN + ZERO_LEN;
        int nPadLen = nPadSaltBodyZeroLen % 8;
        nPadLen = (nPadLen == 0 ? nPadLen : 8 - nPadLen);

        ByteBuffer srcBuf = ByteBuffer.allocate(8);
        int srcIndex = 0;
        srcBuf.put(srcIndex++, (byte) (((int)(Math.random()*255) & 0xf8) | nPadLen));
        while ((nPadLen--) > 0) {
            srcBuf.put(srcIndex++, (byte)(Math.random()*255));
        }

        ByteBuffer prev_plainTeaBufBlock = ByteBuffer.allocate(8);
        ByteBuffer prev_cryptBufBlock = ByteBuffer.allocate(8);
        for (int i=0; i<8; i++) {
            prev_plainTeaBufBlock.put(i, (byte) 0);
            prev_cryptBufBlock.put(i, (byte) 0);
        }

        ByteBuffer block = ByteBuffer.allocate(8);

        for (int i=0; i<SALT_LEN; ) {
            if (srcIndex < 8) {
                srcBuf.put(srcIndex++, (byte)(Math.random()*255));
                i++;
            }
            if (srcIndex == 8){
                encryptBlock(version,keyBuf, srcBuf, prev_plainTeaBufBlock, prev_cryptBufBlock, block);
                for (int j = 0; j < 8; j++) {
                    outputBuffer.put(outputIndex++, block.get(j));
                }
                srcIndex = 0;
            }
        }

        while (inputBufferLen > 0) {
            if (srcIndex < 8) {
                srcBuf.put(srcIndex++, inputBuffer.get(inputIndex++));
                inputBufferLen--;
            }
            if (srcIndex == 8) {
                encryptBlock(version,keyBuf, srcBuf, prev_plainTeaBufBlock, prev_cryptBufBlock, block);
                for (int j = 0; j < 8; j++) {
                    outputBuffer.put(outputIndex++, block.get(j));
                }
                srcIndex = 0;
            }
        }

        for (int i=0; i<ZERO_LEN; ) {
            if (srcIndex < 8) {
                srcBuf.put(srcIndex++, (byte) 0);
                i++;
            }
            if (srcIndex == 8) {
                encryptBlock(version,keyBuf, srcBuf, prev_plainTeaBufBlock, prev_cryptBufBlock, block);
                for (int j = 0; j < 8; j++) {
                    outputBuffer.put(outputIndex++, block.get(j));
                }
                srcIndex = 0;
            }
        }

        return outputIndex;
    }

    private static void encryptBlock(byte version, ByteBuffer key, ByteBuffer plainBufBlock, ByteBuffer prev_plainTeaBufBlock, ByteBuffer prev_cryptBufBlock, ByteBuffer cryptBufBlock) {
        ByteBuffer cryptTeaBufBlock = ByteBuffer.allocate(8);
        ByteBuffer plainTeaBufBlock = ByteBuffer.allocate(8);

        for (int j=0; j<8; j++) {
            plainTeaBufBlock.put(j, (byte) (plainBufBlock.get(j) ^ prev_cryptBufBlock.get(j)));
        }
        teaEncryptECB(plainTeaBufBlock, key, cryptTeaBufBlock);
        for (int j=0; j<8; j++) {
            if ( version == 1 ) {
                cryptBufBlock.put(j, (cryptTeaBufBlock.get(j)));
            } else {
                cryptBufBlock.put(j, (byte) (cryptTeaBufBlock.get(j) ^ prev_plainTeaBufBlock.get(j)));
            }
        }

        for (int j=0; j<8; j++) {
            prev_plainTeaBufBlock.put(j, plainTeaBufBlock.get(j));
            prev_cryptBufBlock.put(j, cryptBufBlock.get(j));
        }
    }

    private static void teaEncryptECB(ByteBuffer inBuf, ByteBuffer key, ByteBuffer outBuf) {
        long y = inBuf.getInt(0) & BASE;
        long z = inBuf.getInt(4) & BASE;

        long[] k = new long[4];
        for (int i=0; i<4; i++) {
            k[i] = key.getInt(i*4) & BASE;
        }

        long sum = 0;
        for (int i=0; i<ROUNDS; i++) {
            sum = (sum + DELTA) & BASE;
            y = (y + ((z<<4)+k[0] ^ z+sum ^ (z>>5)+k[1])) & BASE;
            z = (z + ((y<<4)+k[2] ^ y+sum ^ (y>>5)+k[3])) & BASE;
        }

        outBuf.putInt(0, (int)y);
        outBuf.putInt(4, (int)z);
    }

    public static int encryptLen(int dataLen) {
        int length = dataLen + 1 + SALT_LEN + ZERO_LEN;
        int nPadLen = length % 8;
        nPadLen = (nPadLen == 0 ? nPadLen : 8 - nPadLen);
        return length + nPadLen;
    }

    /**
     * 解密
     * @param version 版本 目前可填1或者2
     * @param inputBuffer 密文
     * @param inputBufferLen 密文长度
     * @param outputBuffer 明文（需先开好足够大的空间）
     * @return 明文长度
     * 解密主要逻辑：
     * cryptBuf XOR prev_plainTeaBuf -> cryptTeaBuf
     * cryptTeaBuf -> (Using Tea Algorithm To Decrypt) -> plainTeaBuf
     * plainTeaBuf XOR prev_cryptBuf -> plainBuf
     */
    public static synchronized int decrypt(byte version, ByteBuffer inputBuffer, int inputBufferLen, byte[] key, ByteBuffer outputBuffer) {

        if (version != 1 && version != 2 ) {
            throw new RuntimeException("version must be 1 or 2");
        }

        if (key.length != KEYLEN) {
            return -1;
        }
        ByteBuffer keyBuf = ByteBuffer.allocate(16);
        for (int i=0; i<KEYLEN; i++) {
            keyBuf.put(i, key[i]);
        }

        int inputIndex = 0, outputIndex = 0;

        if (inputBufferLen % 8 != 0 || inputBufferLen < 16) {
            System.out.println("inputBufferLen is not correct, inputBufferLen = " + inputBufferLen);
            return -1;
        }

        ByteBuffer prev_cryptBufBlock = ByteBuffer.allocate(8);
        ByteBuffer prev_plainTeaBufBlock = ByteBuffer.allocate(8);
        for (int i=0; i<8; i++) {
            prev_cryptBufBlock.put(i, (byte) 0);
            prev_plainTeaBufBlock.put(i, (byte) 0);
        }

        ByteBuffer block = ByteBuffer.allocate(8);
        decryptBlock(version,keyBuf, inputBuffer, prev_cryptBufBlock, prev_plainTeaBufBlock, block);
        int nPadLen = block.get(0) & 0x7;

        int outputBufferLen = inputBufferLen - 1 - nPadLen - SALT_LEN - ZERO_LEN;
        if (outputBufferLen < 0) {
            System.out.println("outputBufferLen is not correct, outputBufferLen = " + outputBufferLen);
            return -1;
        }

        int beginIndex = 0, endIndex = 8;

        if (1 + nPadLen + SALT_LEN < 8) {
            inputIndex = 1 + nPadLen + SALT_LEN;
            for (int i = 0; i < 8 - (1 + nPadLen + SALT_LEN); i++) {
                outputBuffer.put(outputIndex++, block.get(inputIndex + i));
            }
            inputIndex = 8;
        } else {
            beginIndex = 1 + nPadLen + SALT_LEN - 8;
            inputIndex = 8;
        }

        ByteBuffer srcBuf = ByteBuffer.allocate(8);
        int srcIndex = 0;
        while (inputIndex < inputBufferLen) {
            if (srcIndex < 8) {
                srcBuf.put(srcIndex, inputBuffer.get(inputIndex));
                srcIndex++;
                inputIndex++;
            }
            if (srcIndex == 8) {
                decryptBlock(version,keyBuf, srcBuf, prev_cryptBufBlock, prev_plainTeaBufBlock, block);
                for (int i = beginIndex; i < endIndex; i++) {
                    outputBuffer.put(outputIndex++, block.get(i));
                }
                beginIndex = 0;
                if (outputIndex + 1 == outputBufferLen + 8 - ZERO_LEN - 1) {
                    endIndex = 8 - ZERO_LEN;
                }
                srcIndex = 0;
            }
        }

        return outputIndex;
    }

    private static void decryptBlock(byte version, ByteBuffer key, ByteBuffer cryptBufBlock, ByteBuffer prev_cryptBufBlock, ByteBuffer prev_plainTeaBufBlock, ByteBuffer plainBufBlock) {
        ByteBuffer cryptTeaBufBlock = ByteBuffer.allocate(8);
        ByteBuffer plainTeaBufBlock = ByteBuffer.allocate(8);

        for (int j=0; j<8; j++) {
            if (version == 1 ) {
                cryptTeaBufBlock.put(j, (cryptBufBlock.get(j)));
            } else {
                cryptTeaBufBlock.put(j, (byte) (cryptBufBlock.get(j) ^ prev_plainTeaBufBlock.get(j)));
            }
        }
        teaDecryptECB(cryptTeaBufBlock, key, plainTeaBufBlock);
        for (int j=0; j<8; j++) {
            plainBufBlock.put(j, (byte) (plainTeaBufBlock.get(j) ^ prev_cryptBufBlock.get(j)));
        }

        for (int j=0; j<8; j++) {
            prev_plainTeaBufBlock.put(j, plainTeaBufBlock.get(j));
            prev_cryptBufBlock.put(j, cryptBufBlock.get(j));
        }
    }

    private static void teaDecryptECB(ByteBuffer inBuf, ByteBuffer key, ByteBuffer outBuf) {
        long y = inBuf.getInt(0) & BASE;
        long z = inBuf.getInt(4) & BASE;

        long[] k = new long[4];
        for (int i=0; i<4; i++) {
            k[i] = (long) key.getInt(i*4);
        }

        long sum = (DELTA << LOG_ROUNDS) & BASE;
        for (int i=0; i<ROUNDS; i++)
        {
            z = (z - ((y<<4)+k[2] ^ y+sum ^ (y>>5)+k[3])) & BASE;
            y = (y - ((z<<4)+k[0] ^ z+sum ^ (z>>5)+k[1])) & BASE;
            sum = (sum - DELTA) & BASE;
        }

        outBuf.putInt(0, (int)y);
        outBuf.putInt(4, (int)z);
    }

    public static byte[] hexToBytes(String s) {
        s = s.toUpperCase();
        int len = s.length() / 2;
        int ii = 0;
        byte[] bs = new byte[len];
        char c;
        int h;
        for (int i = 0; i < len; i++) {
            c = s.charAt(ii++);
            if (c <= '9') {
                h = c - '0';
            } else {
                h = c - 'A' + 10;
            }
            h <<= 4;
            c = s.charAt(ii++);
            if (c <= '9') {
                h |= c - '0';
            } else {
                h |= c - 'A' + 10;
            }
            bs[i] = (byte) h;
        }
        return bs;
    }

    public static final String bytesToHexString(byte[] bArray) {
        StringBuffer sb = new StringBuffer(bArray.length);
        String sTemp;
        for (int i = 0; i < bArray.length; i++) {
            sTemp = Integer.toHexString(0xFF & bArray[i]);
            if (sTemp.length() < 2)
                sb.append(0);
            sb.append(sTemp.toUpperCase());
        }
        return sb.toString();
    }

    /**
     int sdkappid = 1400060496; //sdkappid
    int groupid = 80103;   //房间id
    int expired = 86400; //过期时间，秒，建议填写 600 + 
    String key = "6c84f80d86e6ac3a"; //请在控制台 - App基础设置 - 应用信息 中找到 【权限密钥】
     * @return
     */
    public static Map getSig(int sdkappid,int groupid,int expired,String key) {
    	Map resultMap = new HashMap();
    	
        ByteBuffer src = ByteBuffer.allocate(13);
        src.put((byte) 2);
        src.putInt(sdkappid);
        src.putInt(groupid);
        int now = (int) (new Date().getTime() / 1000);
        
        System.out.println( now+expired );
        src.putInt(now+expired);
        src.flip();

        ByteBuffer encBuffer = ByteBuffer.allocate(50);
        int len = Crypt.encrypt((byte) 1,src,13,key.getBytes(),encBuffer);
        byte[] encBytes = new byte[len];
        encBuffer.get(encBytes, 0, encBytes.length);
        resultMap.put("sig", bytesToHexString(encBytes));
        System.out.println(bytesToHexString(encBytes)  );

        
        ByteBuffer srcBuffer = ByteBuffer.allocate(13);
        Crypt.decrypt((byte) 1,ByteBuffer.wrap(encBytes),encBytes.length,key.getBytes(),srcBuffer);
        srcBuffer.order(ByteOrder.BIG_ENDIAN);

        System.out.println(srcBuffer.get());
        System.out.println(srcBuffer.getInt());
        System.out.println(srcBuffer.getInt());
        System.out.println(srcBuffer.getInt());

    		
    	return resultMap;	
    } 
    public static void main(String[] args ) {
        
    	System.out.println(getSig(1400060496,80103, 86400, "6c84f80d86e6ac3a"));
    }

}